using System;
using System.IO.Ports;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Socket = System.Net.Sockets.Socket;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Net.NetworkInformation;

using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Net;
using System.IO;

namespace FEZ_Cobra_Console_Application1
{
    public class Wifi
    {
        public static bool wifi_event = false;
        public static bool wifi_last_status = false;
        public static bool network_is_read = false;
        public static bool wifi_is_init = false;    //has wifi already been initialized

        public static bool continueToProcess = true;    //kill switch for thread socket listener
        public static bool connected = false;           //global flag
        public static bool commGuardianActive = true;
        public static bool sendingData = true;

        public static ManualResetEvent NetworkAvailablityBlocking = null;
        public static Wireless80211 WiFiSettings = null;

        public static Socket server;
        public static IPEndPoint remoteEndPoint;
        public static Commander c;
        public static DateTime lastHeardFromServer = DateTime.Now;
        public static Thread listeningThread;
        public static Thread guardianThread;
        public static Thread beaconThread;

        public static IPAddress serverIPAddress = IPAddress.Parse("67.79.220.122"); //default value

        /// <summary>
        /// Init wifi communication settings
        /// </summary>
        public Wifi()
        {
            if (wifi_is_init)
                return;

            //Commander is used to issue commands to the vehicle
            c = new Commander();

            NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
            if (!WiFi.IsEnabled)
            {
                WiFi.Enable(SPI.SPI_module.SPI2, (Cpu.Pin)FEZ_Pin.Digital.IO2, (Cpu.Pin)FEZ_Pin.Interrupt.IO26);
            }

            // WiFi settings
            NetworkInterface[] netif = NetworkInterface.GetAllNetworkInterfaces();
            
            for (int index = 0; index < netif.Length; ++index)
            {
                if (netif[index] is Wireless80211)
                {
                    WiFiSettings = (Wireless80211)netif[index];

                }
            }

            //WIFI Information goes here
            WiFiSettings.Ssid = "AndroidAP0355";
            WiFiSettings.Encryption = Wireless80211.EncryptionType.None;
            WiFiSettings.Authentication = Wireless80211.AuthenticationType.Open;

            //WiFiSettings.Ssid = "Texas Tau";
            //WiFiSettings.Encryption = Wireless80211.EncryptionType.WPA;
            //WiFiSettings.Authentication = Wireless80211.AuthenticationType.Shared;
            //WiFiSettings.PassPhrase = "sorostitute"; 
        }

        /// <summary>
        /// Connects FEZ Wifi module to the specified Wifi Network
        /// </summary>
        /// <returns></returns>
        public bool connectWifi()
        {
            Wireless80211.SaveConfiguration(new Wireless80211[] { WiFiSettings }, false);
            NetworkAvailablityBlocking = new ManualResetEvent(false);
            if (!WiFi.IsLinkConnected)
            {
                Debug.Print("Waiting for WiFi link!");
                NetworkAvailablityBlocking.Reset();
                while (!NetworkAvailablityBlocking.WaitOne(5000, false))
                {
                    if (!WiFi.IsLinkConnected)
                    {
                        Debug.Print("WiFi link is not available yet! Wrong AP settings?Still waiting.");
                        Debug.Print("Still waiting.");
                    }
                    else
                        break;
                }
            }

            try
            {
                if (!WiFiSettings.IsDhcpEnabled)
                    WiFiSettings.EnableDhcp();// This function is blocking
                else
                {
                    WiFiSettings.RenewDhcpLease();// This function is blocking
                }
                network_is_read = true;
                Debug.Print("Network settings:");
                Debug.Print("IP Address: " + WiFiSettings.IPAddress);
                Debug.Print("Subnet Mask: " + WiFiSettings.SubnetMask);
                Debug.Print("Default Getway: " + WiFiSettings.GatewayAddress);
                Debug.Print("DNS Server: " + WiFiSettings.DnsAddresses[0]);

                Thread.Sleep(1000);
                //for dynamic IP address, get proper server IP
                try
                {
                    IPAddress[] addressList = Dns.GetHostEntry("mattpapageorge.dyndns.org").AddressList;
                    if (addressList.Length >= 1)
                    {
                        serverIPAddress = addressList[0];
                    }
                    Debug.Print("Server Host IP should be: " + serverIPAddress);
                }
                catch
                {
                    Debug.Print("Failed to get server IP address");
                }

            }
            catch
            {
                Debug.Print("DHCP Failed");
                return false;
            }

            Debug.Print("WiFi link is ready!");
            return true;
        }

        /// <summary>
        /// Does all of the networking specific setup
        /// </summary>
        public bool connectNetwork()
        {
            // Create a socket, bind it to the server's port, and listen for client 
            // connections.
            //server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            //SERVER IP NEEDS TO BE MENTIONED HERE
            //If server is behind router the port needs to be forwarded to the local IP address
            //If there is a firewall on the windows machine it must be turned off, even Windows Firewall
            remoteEndPoint = new IPEndPoint(serverIPAddress, 441);


            try
            {
                //server.Connect(remoteEndPoint);
                connected = true;
            }
            catch (Exception adsf)
            {
                Debug.Print("Socket gave exception when trying to connect. Fatal error");
                connected = false;
                return false;
            }

            //if connected send welcome message right away
            server.SendTo(UTF8Encoding.UTF8.GetBytes("Connected to client: FEZ Cobra, UGSV"), remoteEndPoint);
            Debug.Print("Test message has been sent");

            return true;
        }

        /////// <summary>
        /////// This thread needs to run constantly listening for new commands. TCP Version
        /////// </summary>
        ////public void listenToCommands()
        ////{
        ////    if ((server == null) || (!connected))
        ////        return; //cannot perform these actions, must return

        ////    try
        ////    {
        ////        int bytesAvailable = 0;
        ////        while (continueToProcess)
        ////        {
        ////            bytesAvailable = server.Available;
        ////            if ((bytesAvailable > 0))
        ////            {
        ////                byte[] incomingCommand = new byte[server.Available];
        ////                server.Receive(incomingCommand);
        ////                c.processCommands(incomingCommand);
        ////            }
        ////            //// Process the client request.  true means asynchronous.
        ////            //new ProcessClientRequest(server, true);
        ////            Thread.Sleep(1); //sleep for 1 ms. This may change
        ////        }
        ////    }
        ////    catch (Exception connErr)
        ////    {
        ////        connected = false;
        ////        Debug.Print("Something went wrong with connection. Need to restart");
        ////    }
            
        ////    //IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, c_port);
        ////    //server.Bind(localEndPoint);
        ////    //server.Listen(Int32.MaxValue);

        ////    //while (true)
        ////    //{
        ////    //    // Wait for a client to connect.
        ////    //    Socket clientSocket = server.Accept();

        ////    //    // Process the client request.  true means asynchronous.
        ////    //    new ProcessClientRequest(clientSocket, true);
        ////    //}
        ////}

        /// <summary>
        /// This thread needs to run constantly listening for new commands. UDP Version
        /// </summary>
        public void listenToCommands()
        {
            String inString = "";

            HttpWebRequest WebRequestObject;

            //WebRequestObject.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)";
            //WebRequestObject.Referer = "http://www.google.com/";

            // Request response:
            WebResponse Response;

            //open data stream
            Stream webStream;

            //Open data stream
            StreamReader Reader;


            //if ((server == null) || (!connected))
            //    return; //cannot perform these actions, must return

            try
            {
                while (true)
                {
                    try
                    {
                        WebRequestObject = (HttpWebRequest)HttpWebRequest.Create("http://" + serverIPAddress.ToString() + ":441/listener");
                        // Request response:
                        Response = WebRequestObject.GetResponse();
                        WebRequestObject.Timeout = 50;
                        WebRequestObject.KeepAlive = false;

                        //open data stream
                        webStream = Response.GetResponseStream();

                        //Open data stream
                        Reader = new StreamReader(webStream);
                        webStream.Close();

                        inString = Reader.ReadToEnd();
                        Reader.Close();
                        //Debug.Print(inString);

                        c.processCommands(inString);
                        WebRequestObject.Dispose();

                        Thread.Sleep(1000);
                    }
                    catch (Exception sd)
                    {
                        Debug.Print("NO WEB CONTACT, waiting 10 seconds");
                        Thread.Sleep(10000);
                    }
                }
                
            }
            catch (Exception connErr)
            {
                connected = false;
                Debug.Print("Something went wrong with connection. Need to restart");
            }

            //IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, c_port);
            //server.Bind(localEndPoint);
            //server.Listen(Int32.MaxValue);

            //while (true)
            //{
            //    // Wait for a client to connect.
            //    Socket clientSocket = server.Accept();

            //    // Process the client request.  true means asynchronous.
            //    new ProcessClientRequest(clientSocket, true);
            //}
        }

        /// <summary>
        /// Does everything to get the Wifi going right away
        /// </summary>
        public void CommunicationsSubsystemFacade()
        {
            connectWifi();
            while(!connectNetwork())
            {
                //should we keep trying here???
            }
            //start the listening stuff in a different thread
            listeningThread = new Thread(new ThreadStart(listenToCommands));
            listeningThread.Start();
            //NEED the guardian thread with TCP
            //guardianThread = new Thread(new ThreadStart(communicationsGuardian));
            //guardianThread.Start();
            //Now the beacon stuff
            beaconThread = new Thread(new ThreadStart(beacon));
            beaconThread.Start();
            
        }

        /// <summary>
        /// Ensures that everything remains connected. DO NOT need this with UDP
        /// </summary>
        public void communicationsGuardian()
        {
            //Wait a while before starting the comm guardian
            Thread.Sleep(7000);
            while (commGuardianActive)
            {
                Thread.Sleep(5000);
                //if its been 15 seconds since the last communication, assume that the connection is dead
                if (((DateTime.Now.Ticks - lastHeardFromServer.Ticks) > TimeSpan.TicksPerSecond * 15)||(!connected))
                {
                    //indicate connection has dropped so no one tries to use it in the meantime
                    connected = false;
                    //first we need to verify that everything is dead
                    try
                    {
                        //kill daemon if not already killed
                        continueToProcess = false;
                        //should not be a surprise if the below fails. Need to try anyway though in case connection is ok
                        //send_TCP(Encoding.UTF8.GetBytes("CONNECTION_DEAD"));
                        server.Close();
                    }
                    catch (Exception asd)
                    {
                        Debug.Print("Error properly closing connection by communications guardian");
                    }
                    try
                    {
                        if (listeningThread != null)
                        {
                            listeningThread.Abort();
                        }
                    }
                    catch (Exception sss)
                    {
                        Debug.Print("Error properly closing listen thread by communications guardian");
                    }
                    //Now we need to restart everything
                    while (!connectNetwork())
                    {
                        //try every 5 seconds
                        Thread.Sleep(5000);
                        //should we keep trying here???
                    }
                    continueToProcess = true; //reset listening flag
                    lastHeardFromServer = DateTime.Now;

                    listeningThread = new Thread(new ThreadStart(listenToCommands));
                    listeningThread.Start();
                    //we should be ok from here on????
                }

            }
        }

        /// <summary>
        /// Send byte array to control server via UDP
        /// </summary>
        /// <param name="sendB"></param>
        public static void send_UDP(byte[] sendB)
        {
            //sendingData = true;
            if (connected && (server != null) && sendingData)
            {
                server.SendTo(sendB, remoteEndPoint);
            }
            //sendingData = false;
        }

        /// <summary>
        /// Sends the readout information every beacon interval
        /// </summary>
        public void beacon()
        {
            Readouts readoutInstance = Readouts.getInstance();

            while (true)
            {
                if (connected)
                {
                    try
                    {
                        send_UDP(Encoding.UTF8.GetBytes(readoutInstance.getReadouts()));
                        
                    }
                    catch (Exception sdsd)
                    {
                        Debug.Print("Lost connection to server. Will attempt to reconnect.");
                        connected = false;
                    }
                }
                Thread.Sleep(1500);
            }

        }




        //internal sealed class ProcessClientRequest
        //{
        //    private Socket m_clientSocket;

        //    /// <summary>
        //    /// The constructor calls another method to handle the request, but can 
        //    /// optionally do so in a new thread.
        //    /// </summary>
        //    /// <param name="clientSocket"></param>
        //    /// <param name="asynchronously"></param>
        //    public ProcessClientRequest(Socket clientSocket, Boolean asynchronously)
        //    {
        //        m_clientSocket = clientSocket;

        //        if (asynchronously)
        //            // Spawn a new thread to handle the request.
        //            new Thread(ProcessRequest).Start();
        //        else new Thread(ProcessRequest).Start();
        //    }

        //    /// <summary>
        //    /// Processes the request.
        //    /// </summary>
        //    private void ProcessRequest()
        //    {
        //        const Int32 c_microsecondsPerSecond = 1000000;

        //        // 'using' ensures that the client's socket gets closed.
        //        using (m_clientSocket)
        //        {
        //            // Wait for the client request to start to arrive.
        //            if (m_clientSocket.Poll(5 * c_microsecondsPerSecond, SelectMode.SelectRead))
        //            {
        //                // If 0 bytes in buffer, then the connection has been closed, 
        //                // reset, or terminated.
        //                if (m_clientSocket.Available == 0)
        //                    return;

        //                // Read the first chunk of the request (we don't actually do 
        //                // anything with it).
        //                // Int32 bytesRead = m_clientSocket.Receive(buffer,m_clientSocket.Available, SocketFlags.None);

        //                // Return a static HTML document to the client.
        //                byte[] dados = new byte[64000];

        //                m_clientSocket.Send(dados);
        //            }
        //        }
        //    }
        //}
        static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            if (e.IsAvailable)
            {
                if (WiFi.IsLinkConnected)
                {
                    if (wifi_last_status != true)
                    {
                        wifi_last_status = true;
                        NetworkAvailablityBlocking.Set();
                    }
                }
            }
            else
            {
                if (!WiFi.IsLinkConnected)
                {
                    if (wifi_last_status != false)
                    {
                        wifi_last_status = false;
                        network_is_read = false;
                    }
                }
            }
        }
    }
}